// 一种特殊的二叉树，也就是堆数据结构，也叫作二叉堆。二叉堆是计算机科学中一种非常著名的数据结构，由于它能高效、快速地找出最大值和最小值，常被应用于优先队列。它也被用于著名的堆排序算法中
// 完全二叉树 + 两极端

// 二叉堆是一种特殊的二叉树，有以下两个特性。
// 1. 它是一棵完全二叉树，表示树的每一层都有左侧和右侧子节点（除了最后一层的叶节点），并且最后一层的叶节点尽可能都是左侧子节点，这叫作结构特性。 
// 2. 二叉堆不是最小堆就是最大堆。最小堆允许你快速导出树的最小值，最大堆允许你快速导出树的最大值。所有的节点都大于等于（最大堆）或小于等于（最小堆）每个它的子节点。这叫作堆特性。


const swap = (array, a, b) => [array[a], array[b]] = [array[b], array[a]];

/**
 * 使用数组来实现二叉堆，对于给定位置 index 的节点，我们已知的有：
 *   1. 它的左侧子节点的位置是 2 * index + 1（如果位置可用）
 *   2. 它的右侧子节点的位置是 2 * index + 2（如果位置可用）
 *   3. 它的父节点位置是 index / 2（如果位置可用）
 * 
 * insert(value)：这个方法向堆中插入一个新的值。如果插入成功，它返回 true，否则返回 false
 * extract()：这个方法移除最小值（最小堆）或最大值（最大堆），并返回这个值
 * findMinimum()：这个方法返回最小值（最小堆）或最大值（最大堆）且不会移除这个值
 * 
 * @class MinHeap
 */
class MinHeap {
  constructor(){
    this.heap = [];
  }

  // 返回左节点值
  getLeftIndex(index) { 
    return 2 * index + 1; 
  } 

  // 返回右节点值
  getRightIndex(index) { 
    return 2 * index + 2; 
  } 

  // 返回父节点值
  getParentIndex(index) { 
    if (index === 0) { 
      return undefined; 
    } 

    return Math.floor((index - 1) / 2); 
  }

  // 思路：指将值插入堆的底部叶节点（数组末尾），然后往前将这个值和它的父节点进行交换，直到父节点小于这个插入的值
  insert(value){
    if (value != null) { 
      this.heap.push(value);
      this.siftUp(this.heap.length - 1);
      return true; 
    } 
    return false;
  }
  siftUp(index){
    let parent = this.getParentIndex(index);
    while(index > 0 && this.heap[parent] > this.heap[index]){
      swap(this.heap, parent, index);
      index = parent;
      parent = this.getParentIndex(parent);
    }
  }

  // 堆长度，节点个数
  size() { 
    return this.heap.length; 
  } 

  // 是否为空
  isEmpty() { 
    return this.size() === 0; 
  } 

  // 第一个堆
  findMinimum() { 
    return this.isEmpty() ? undefined : this.heap[0]; // {1} 
  }

  extract() { 
    if (this.isEmpty()) { 
      return undefined;
    } 
    if (this.size() === 1) { 
      return this.heap.shift();
    } 

    // 如果堆中有不止一个值，我们需要将第一个值移除，存储到一个临时变量中以便在执行完下移操作后，返回它
    const removedValue = this.heap.shift();
    this.siftDown(0);
    return removedValue;
  }
  siftDown(index){
    let element = index; 
    const left = this.getLeftIndex(index);
    const right = this.getRightIndex(index);
    const size = this.size();

    if(left < size && this.heap[element] > this.heap[left]){
      element = left;
    }

    if(right < size && this.heap[element] > this.heap[right]){
      element = right;
    }

    if (index !== element) {
      swap(this.heap, index, element);
      this.siftDown(element);
    }
  }
}

const heap = new MinHeap(); 
heap.insert(2); 
heap.insert(3); 
heap.insert(4); 
heap.insert(5); 
heap.insert(1);

console.log('Heap size: ', heap.size()); // 5 
console.log('Heap is empty: ', heap.isEmpty()); // false 
console.log('Heap min value: ', heap.findMinimum()); // 1

for (let i = 1; i < 10; i++) { 
 heap.insert(i); 
} 
console.log('Extract minimum: ', heap.extract()); // 1


// MaxHeap 类的算法和 MinHeap 类的算法一模一样。不同之处在于我们要把所有>（大于）的比较换成<（小于）的比较。
// this.heap[parent] > this.heap[index] 换成 this.heap[parent] < this.heap[index]
// this.heap[element] > this.heap[left] 换成 this.heap[element] < this.heap[left]
// this.heap[element] > this.heap[right] 换成 this.heap[element] < this.heap[right]